/*
 * sock.c
 *
 * Just a sample of the testing program.
 *
 *  Created on: 2011-08-16
 *      Author: Baron Wang
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <getopt.h>

#include <ev.h>
#include "trans.h"
#include <sys/queue.h>


/*
 * functions in prop.c we need to read line for properites files
 */
extern int   init_properties(const char* file);
extern int   read_config_num(char* field, int def);
extern char* read_config_value(char* field, char* resbuf, const char* def);
extern void  fini_properties();

/*
 * functions in calb.c, the callback functions
 */

extern LIST_HEAD(conn_head, conn) conn_list;

extern int cb_on_new_conn(void *llc, void *ulc, void **out_ulc, tsock_ep *ep);
extern int cb_on_connect(void *llc, void *ulc);
extern int cb_on_timer(int id, void *llc, void *ulc);
extern int cb_read_tcp(void *llc, void* ulc, char* buf, int len);
extern int cb_close(void *llc, void *ulc, int err);
extern int cb_read_udp(void *llc, void* ulc, tsock_ep *from, tsock_ep *to,
    char* buf, int len);
extern int cb_sock_err(void *llc, void *ulc, int err);
extern int cb_listen_err(void *llc, void *ulc, int err);
extern int cb_conn_err(void *llc, void *ulc, int err);

#define MAX_EP_NUM 40

/*
 * The global parameters,
 */
static trans_opt    opt;
static log_opt      l_opt;
static sock_cb      callback;
static sock_opts    opts;
static char         filebuf[40];

/* read the (server) IP and port from the properties if have any
 * they are in the .data and will be zero out when loading
 * but for user, should maunually clean the entry when closed.
 */
static tsock_ep     ep_arr[MAX_EP_NUM];
static int          num_ep = 0;

static tsock_ep     local_ep;

static void*        listen_llc_arr[MAX_EP_NUM];

static void*        connect_llc;
/*
 * The function for the testing use
 */
int load_config(const char* filename){

    int i = 0;
    int type;
    int tmp;
    char ipbuf[100];

    if(init_properties(filename)){
        return -1;
    }

    /* reset all the reset_ep array */
    for(i = 0; i < MAX_EP_NUM; ++i){
        reset_ep(&ep_arr[i]);
    }

    /* init the trans_opt */
    opt.max_socket_num = read_config_num("NUM_MAX_SOCKET", 200);
    opt.t_attr = NULL;
    opt.num_of_worker = read_config_num("NUM_OF_WORKER", 3);
        l_opt.def_lvl = read_config_num("LOG_LEVEL_DEF", 6);
        l_opt.log = NULL;
        l_opt.logfile = read_config_value("LOG_FILE", filebuf, "run.log");

    opt.l_opt = &l_opt;

    /* read the IP and port for the ip array */
    num_ep = read_config_num("IP_NUM", 0);
    if(0 == num_ep){
        printf("no endpoint configured in cfg file.\n");
        return -1;
    }

    for(i = 0; i < num_ep; ++i){

        type = read_config_num("TYPE", -1);

        if(NULL == read_config_value("IP_ADDR", ipbuf, NULL)){
            printf("passing a wrong addr.\n");
            return -1;
        }

        tmp = read_config_num("IP_PORT", -1);
        if(tmp < 0){
            printf("passing a wrong port.\n");
            return -1;
        }

        if(1 == type){
            if(addr_to_ep(ipbuf, SYS_TCP, tmp, &ep_arr[i])){
                printf("failed to convert to ep.\n");
                return -1;
            }

        }else if(0 == type){
            if(addr_to_ep(ipbuf, SYS_UDP, tmp, &ep_arr[i])){
                printf("failed to convert to ep.\n");
                return -1;
            }
        }else{
            printf("get a wrong format.\n");
            return -1;
        }
    }

    /* update the local port, in case it is used */
    type = read_config_num("CLIENT_LOCAL_IP_TYPE", -1);
    if(NULL == read_config_value("CLIENT_LOCAL_IP_ADDR", ipbuf, NULL)){
        printf("passing a wrong local addr.\n");
        return -1;
    }
    tmp = read_config_num("CLIENT_LOCAL_IP_PORT", -1);
    if(tmp < 0){
        printf("passing a wrong local port.\n");
        return -1;
    }
    if(1 == type){
        if(addr_to_ep(ipbuf, SYS_TCP, tmp, &local_ep)){
            printf("failed to convert to ep.\n");
            return -1;
        }

    }else if(0 == type){
        if(addr_to_ep(ipbuf, SYS_UDP, tmp, &local_ep)){
            printf("failed to convert to ep.\n");
            return -1;
        }
    }else{
        printf("get a wrong format.\n");
        return -1;
    }

    fini_properties();

    return 0;
}

void init_static(){
    callback.on_close = cb_close;
    callback.on_conn_err = cb_conn_err;
    callback.on_connect = cb_on_connect;
    callback.on_default_timer = cb_on_timer;
    callback.on_listen_err = cb_listen_err;
    callback.on_new = cb_on_new_conn;
    callback.on_read_tcp = cb_read_tcp;
    callback.on_read_udp = cb_read_udp;
    callback.on_sctp_event = NULL;
    callback.on_sock_err = cb_sock_err;

    opts.ip_tos = 1;
    opts.linger_time = 20;
    opts.rbuf_size = 1024 * 32L;
    opts.sbuf_size = 1024 * 32L;
    opts.opts = SOCK_OPT_MINIMAL;
}

static void usage(){
    puts(

        "usage: sock [ options ] (run as a server)                         \n"
        "       sock [ options ] <-c> (run as a client)                    \n"
        "                                                                  \n"
        "note:  the client will only connect to the first address          \n"
        "       in the properties file loaded, by default (sock.cfg)       \n"
        "                                                                  \n"
        "options: -c        run as a client. (only use first addr in cfg)  \n"
        "         -r        restrct callback, will \"close on error\"      \n"
        "         -M        multicast add toggle, and add the loopback     \n"
        "         -b        will set the blocking socket                   \n"
        "         -B        broadcast toggle, UDP will try to broadcast    \n"
        "         -l        server will listen once                        \n"
        "         -p        the client will use local port, not 0          \n"
        "         -R        server reuse-addr                              \n"
        "         -L <file> will load a specific file                      \n"
        "         -h        show help                                      \n"

    );
}

static void serverloop(){
    char linebuf[200];
    printf("running as a server, type \"help\" for more details.\n");

    while(1){
        sleep(200);
    }


}

static void clientloop(){
    char linebuf[200];
    register char *tmp;

    ssize_t len;

    printf("running as client, type to send messages, \"Ctrl-D\" to quit.\n");

    while(1){
        printf(">> ");
        if(NULL == fgets(linebuf, sizeof(linebuf), stdin)){
            /* read an error or a EOF */
            break;
        }


        /* windows */
        if(NULL != (tmp = strrchr(linebuf, '\r'))){
            *tmp = '\0';
        }

        if(NULL != (tmp = strrchr(linebuf, '\n'))){
            *tmp = '\0';
        }


        len = strlen(linebuf);

        if(linebuf[0] == '\0'){
            /* empty line */
            continue;
        }


        switch(ep_arr->prtl){
            case SYS_TCP:
                trans_send(connect_llc, linebuf, len, NULL);
                break;
            case SYS_UDP:
                trans_send(connect_llc, linebuf, len, &ep_arr[0]);
                break;
            default:
                continue;
        }

    }

    /* clean job */
    puts("\nclosing socket.");
    trans_close(connect_llc);
    return;
}


int main(int argc, char** argv){

    register int i;
    register int res;

    int c;  /* opt */
    int server = 1;

    char* filename = "sock.cfg";
    opterr = 1;

    if(load_config(filename)){
        exit(-1);
    }
    init_static();

    while((c = getopt(argc, argv, "crMbBlpRL:h")) != -1) {

        switch(c){
            case 'c':
                server = 0;
                break;
            case 'r':
                opts.opts |= SOCK_OPT_CLOSE_ERR;
                break;
            case 'M':
                opts.opts |= (SOCK_OPT_MCAST_ADD | SOCK_OPT_MCAST_LOP);
                break;
            case 'b':
                opts.opts &= ~SOCK_OPT_NON_BLOCKING;
                break;
            case 'B':
                opts.opts |= SOCK_OPT_BORADCAST;
                break;
            case 'l':
                opts.opts |= SOCK_OPT_LISTEN_ONCE;
                break;
            case 'p':
                opts.opts |= SOCK_OPT_LOCAL_PORT;
                break;
            case 'R':
                opts.opts |= SOCK_OPT_REUSE_ADDR;
                break;
            case 'L':
                filename = optarg;
                break;
            case 'h':
            case '?':
            default:
                usage();
                return 0;
        }

    }

    /* should be guarded with global mutex */
    res = trans_init(opt);
    if(res){
        printf("%s\n", trans_strerror(res));
        exit(1);
    }

    sleep(1); /* else, wait for each thread to start */
    if(server){
        LIST_INIT(&conn_list);

        for(i = 0; i < num_ep; ++i){
            res = trans_listen(NULL, (void*)i, &listen_llc_arr[i], &ep_arr[i],
                &callback, &opts, NULL);

            if(res){
                printf("%s\n", trans_strerror(res));
                exit(1);
            }
        }
        sleep(1); /* quiet time, suggest 2 sec */

    }else{

        /*
         * actually, it localport or broadcast/multicast not set
         * the local ep is not used. we can passing a NULL.
         *
         * To simplify it, we passing the local endpoint read from
         * properties.
         */

        /* we can write ******
        if(opts.opts & SOCK_OPT_LOCAL_PORT){
            trans_connect(NULL, (void *)1, &connect_llc, &local_ep, &ep_arr[0],
                &callback, &opts, NULL, 2000);
        }else{
            trans_connect(NULL, (void *)1, &connect_llc, NULL, &ep_arr[0],
                &callback, &opts, NULL, 2000);
        }
        or we can write it simply as below because local-port is checked inside
        */
        res = trans_connect(NULL, (void *)1, &connect_llc, &local_ep, &ep_arr[0],
            &callback, &opts, NULL, 2000);

        if(res){
            printf("%s\n", trans_strerror(res));
            exit(1);
        }
    }
    if(server){
        serverloop();
    }else{
        clientloop();
    }

    /* trans_fini should be never called in a always-running system
    trans_fini();
    */
    puts("program quit.\n");

    return 0;
}
